/*
	nscamera.cpp
	
	Definition file for NSCamera class

	This file contains all of the neccessary definitions for the NSCamera class.

	Daniel Randle
	Mar 16 2013
*/

#include <nscamera.h>
#include <logfile.h>
#include <string>
#include <sstream>

#ifdef DEBUG
#include <debug.h>
#endif

/*
	class NSCamera

	This class is implemented by keeping track of the camera position in a few
	different ways. We have a 3d vector to represent position, another to
	represent orientation, and then a 3x3 matrix to hold the uvn vectors.

	Sensitivity effects the mouse movement speed of the camera and speed effects
	the animation speed of movement.

	Every frame (or update call on camera) the camera's timer has a certain value
	that is compared to the timers value of the previous frame. This time difference
	dt is multiplied by the speed to get the movement for that frame. Therefor,
	speed units are simply units per second.
*/
NSCamera::NSCamera(const std::string & camID, 
	CameraMode mode,
	float _speed,
	float sens, 
	float zoomFac, 
	const NSVec3Df & pos,
	const NSVec3Df & orient,
	const NSVec3Df & rotPoint,
	const NSMatrix3Df & startingOrientation):
	position(pos),
	rotationPoint(rotPoint),
	uvnMatrix(startingOrientation),
	startingOrient(startingOrientation),
	orientation(orient),
	currentMode(mode),
	cameraView(Front),
	flying(false),
	strafing(false),
	elevating(false),
	frontOrBack(FRONT),
	leftOrRight(RIGHT),
	upOrDown(UP),
	speed(_speed),
	sensitivity(sens),
	zoomFactor(zoomFac),
	lastTime(0.0f),
	ID(camID)
{
	_recalcUVNMatrix();
	// Set the camera to the initial orientation wanted by the user
	LogFile("Creating Camera with name " + ID);
}

NSCamera::~NSCamera()
{
	// No new memory calls in camera as of now (yeah i thought about it)
	LogFile("Deleting Camera with name " + ID);
	LogFile("Camera Deleted");
}


/*
	_recalcUVNMatrix

	This function should be called every time the camera's orientation is changed.
	It will recalculate the uvn vectors, using the camera orientation, by taking the
	cross products so that there are no rounding precision erros - that would cause
	all sorts of wierd rotation stuff to happen.
*/
void NSCamera::_recalcUVNMatrix()
{
	// Create new vectors and assing the starting uvn orientations to them
	NSVec3Df uVec = startingOrient.getRow(0);
	NSVec3Df vVec = startingOrient.getRow(1);
	NSVec3Df nVec = startingOrient.getRow(2);

	// Rotate the up vector and the verticle vector by the current neg cam orientation
	// Needs to be neg cause the cam just generates a transform to multiply the other
	// points by, therefor we are not actually rotating the camera but instead are
	// rotating the world around the camera.
	uVec.rotate(-orientation.x,-orientation.y,-orientation.z);
	vVec.rotate(-orientation.x,-orientation.y,-orientation.z);

	// Now that we have the up vector and the verticle vector rotated, we can take the cross
	// product to get the normal vector.
	nVec = uVec.cross(vVec);

	// Now recalculate the up vector by taking the cross of the verticle and normal vectors..
	// This will make sure the axis are all exactly perpendicular with no round errors
	uVec = vVec.cross(nVec);
	uVec.normalize(); // normalize them all duh
	vVec.normalize();
	nVec.normalize();
	uvnMatrix.setData(uVec, vVec, nVec); // And now set the uvnMatrix to contain the new rot info
}

/*
	rotate

	This simply adds the rotation to the current cam orientation and then calls
	the internal _recalcUVNMatrix func.
*/
void NSCamera::rotate(float xAng,float yAng, float zAng)
{
	rotate(NSVec3Df(xAng,yAng,zAng));
}

// Overloaded rotate - use 3d vec
void NSCamera::rotate(const NSVec3Df & toRotate)
{
	orientation += toRotate;
	_recalcUVNMatrix();
}

/*
	translate

	Add translation to current position.
*/
void NSCamera::translate(float xTrans, float yTrans, float zTrans)
{
	position.x += xTrans;
	position.y += yTrans;
	position.z += zTrans;
}

// Overload translate - use 3d vec
void NSCamera::translate(const NSVec3Df & amount)
{
	translate(amount.x,amount.y,amount.z);
}

/*
	move

	Move the camera with the mouse based. This movement is always in the x and
	y directions - sort of a strafing action.

	Sensitivity is important in this function as each cursor position change is
	multiplied by it.

	Todo Maybe : Seperate rotation and movement sensitivities.. sometimes you want higher
	of one but not the other.
*/
void NSCamera::move(int xMouseChange, int yMouseChange)
{
	if (currentMode == FREE)
	{
		translate(uvnMatrix.getRow(UVector) * -xMouseChange*sensitivity * .9);
		translate(uvnMatrix.getRow(VVector) * yMouseChange*sensitivity * .9);
	}
	else
	{
		translate(xMouseChange*-sensitivity,yMouseChange*sensitivity, 0.0f);
	}
}

/*
	turn

	This is the rotation control based on camera movement - we multiply the change by sensitivity
	depending on the camera mode - the camera will rotate around the rotationPoint or as it would
	in a fps.
*/
void NSCamera::turn(int xMouseChange, int yMouseChange)
{
	// The negatives here are a preference thing.. basically Alex that pain in the ass
	// wants rotation by default to be opposite of the normal for focuse mode
	if (currentMode == FREE)
		rotate(yMouseChange*-sensitivity,0.0f,xMouseChange*sensitivity);
	else
		rotate(yMouseChange*sensitivity,0.0f,xMouseChange*-sensitivity);
}

/*
	zoom

	For zoom we just translate the camera in the z direction by the zoomFactor
	if in focus mode, and if in free mode we move it in the direction of the
	normal vector. inOrOut is either 1 or -1 which is why it determines the
	direction of the zoom.
*/
void NSCamera::zoom(Direction inOrOut)
{
	if (currentMode == FREE)
		translate(uvnMatrix.getRow(NVector) * inOrOut * zoomFactor);
	else
		translate(0.0f,0.0f,inOrOut*zoomFactor);
}

/*
	fly

	Will set the animation flag for flying and return true if it changes
*/
bool NSCamera::fly(Direction _frontOrBack)
{
	frontOrBack = _frontOrBack;
	if (!flying)
	{
		flying = true;
		return flying;
	}
	return false;
}

bool NSCamera::stopFlying()
{
	if (!flying)
		return flying;

	flying = false;
	return !flying;
}

bool NSCamera::strafe(Direction _leftOrRight)
{
	leftOrRight = _leftOrRight;
	if (!strafing)
	{
		strafing = true;
		return strafing;
	}
	return false;
}

bool NSCamera::stopStrafing()
{
	if (!strafing)
		return strafing;

	strafing = false;
	return !strafing;
}

bool NSCamera::elevate(Direction _upOrDown)
{
	upOrDown = _upOrDown;
	if (!elevating)
	{
		elevating = true;
		return elevating;
	}
	return false;
}

bool NSCamera::stopElevating()
{
	if (!elevating)
		return elevating;

	elevating = false;
	return !elevating;
}

NSMatrix4Df NSCamera::getTransform()
{
	NSMatrix4Df rotateTransform = getRotationTransform();
	NSMatrix4Df translateTransform;

	translateTransform.translate(position.x,position.y,position.z);
	
	if (currentMode == FREE)
		return rotateTransform * translateTransform;

	else
	{
		rotateTransform.translate(rotationPoint.x,rotationPoint.y,rotationPoint.z);
		return translateTransform * rotateTransform;
	}
}

NSVec3Df NSCamera::getPosition(CameraMode which)
{
	if (which == FREE)
	{
		if (currentMode == FREE)
			return position;
		else
		{
			NSVec3Df wpos;
			toggleCamera();
			wpos = position;
			toggleCamera();
			return wpos;
		}
	}
	else
	{
		if (currentMode == FOCUS)
			return position;
		else
		{
			NSVec3Df wpos;
			toggleCamera();
			wpos = position;
			toggleCamera();
			return wpos;
		}
	}
}

NSVec3Df NSCamera::getOrientation() const
{
	return orientation;
}

float NSCamera::getSpeed() const
{
	return speed;
}

NSVec3Df NSCamera::getUVNVec(UVNChoice which) const
{
	return uvnMatrix.getRow(which);
}

NSCamera::CameraMode NSCamera::getCameraMode() const
{
	return currentMode;
}

void NSCamera::setSpeed(float unitPerSecond)
{
	speed = unitPerSecond;
}

void NSCamera::setSensitivity(float sens)
{
	sensitivity = sens;
}

void  NSCamera::setRotationPoint(const NSVec3Df & point)
{
	if (currentMode == FOCUS)
	{
		NSMatrix4Df rotateTransform = getRotationTransform();
		NSVec4Df temp(point.x,point.y,point.z,1.0f);
		NSVec4Df temp2(rotationPoint.x,rotationPoint.y,rotationPoint.z,1.0f);
		temp = rotateTransform * temp;
		temp2 = rotateTransform * temp2;
		position.x -= (temp.x - temp2.x);
		position.y -= (temp.y - temp2.y);
		position.z -= (temp.z - temp2.z);
	}
	rotationPoint = point;
	cameraView = Front;  // Resetting it here so it cycles through the views normally for a newly selected object
}

void  NSCamera::setPosition(const NSVec3Df & point)
{
	position = point;
}

void  NSCamera::setOrientation(const NSVec3Df & orient)
{
	rotate(orientation * -1);  // Reset orientation
	rotate(orient);            // rotate to new orientation
}

void NSCamera::setCameraMode(CameraMode mode)
{
	if (mode != currentMode)
	{
		if (currentMode == FREE)
		{
			NSVec4Df temp(rotationPoint.x,rotationPoint.y,rotationPoint.z,1.0);
			NSVec4Df temp2(position.x,position.y,position.z,1.0);
			NSMatrix4Df rot = getRotationTransform();
			rot.translate(-rotationPoint.x,-rotationPoint.y,-rotationPoint.z);
			temp =  rot * temp;
			temp2 = rot * temp2;
			temp.rotate(orientation.x,orientation.y,orientation.z);
			position.x = temp2.x - temp.x;
			position.y = temp2.y - temp.y;
			position.z = temp2.z - temp.z;
		}
		else
		{
			NSVec4Df temp(position.x,position.y,position.z,1.0);
			temp = NSMatrix4Df::getRotationMatrix(-orientation.x,-orientation.y,-orientation.z) * temp;
			position.x = rotationPoint.x + temp.x;
			position.y = rotationPoint.y + temp.y;
			position.z = rotationPoint.z + temp.z;
		}
	}
	currentMode = mode;
}

void NSCamera::setTimer(boost::timer * _timer)
{
	timer = _timer;
}

void NSCamera::setView(CameraView view)
{
	setOrientation(NSVec3Df());
	cameraView = view;
	switch (cameraView)
	{
	case (Top):
		if (currentMode == FREE)
			position = rotationPoint;
		else
			position = NSVec3Df();
		position.z += 80;
		return;
	case (Iso):
		if (currentMode == FREE)
		{
			toggleCamera();
			position = NSVec3Df();
			rotate(45.0f,0.0f,45.0f);
			position.z += 120;
			toggleCamera();
		}
		else
		{
			position = NSVec3Df();
			rotate(45.0f,0.0f,45.0f);
			position.z += 120;
		}
		return;
	case (Front):
		if (currentMode == FREE)
		{
			toggleCamera();
			position = NSVec3Df();
			rotate(80.0f,0.0f,0.0f);
			position.z += 20;
			position.y -= 3;
			toggleCamera();
		}
		else
		{
			position = NSVec3Df();
			rotate(80.0f,0.0f,0.0f);
			position.z += 20;
			position.y -= 3;
		}
		return;
	}
}

void NSCamera::changeSpeed(float amount)
{
	speed += amount;
}

void NSCamera::changeSensitivity(float amount)
{
	sensitivity += amount;
}

void NSCamera::toggleCamera()
{
	if (currentMode == FREE)
		setCameraMode(FOCUS);
	else
		setCameraMode(FREE);
}

void NSCamera::toggleView()
{
	if (cameraView == Top)
		setView(Iso);
	else if (cameraView == Iso)
		setView(Front);
	else
		setView(Top);
}

void NSCamera::quickRotate()
{
	if (currentMode == FREE)
	{
		toggleCamera();
		rotate(0.0f,0.0f,-90.0f);
		toggleCamera();
		return;
	}
	rotate(0.0f,0.0f,-90.0f);
}

void NSCamera::update()
{
	float currentTime = timer->elapsed();
	float dt = currentTime - lastTime;
	lastTime = currentTime;

	if (currentMode == FREE)
	{
		if (strafing)
			translate(uvnMatrix.getRow(UVector) * leftOrRight * speed * dt);
		if (elevating)
			translate(uvnMatrix.getRow(VVector) * upOrDown * speed * dt);
		if (flying)
			translate(uvnMatrix.getRow(NVector) * frontOrBack * speed * dt);
	}
	else
	{
		if (strafing)
			translate(leftOrRight * speed * dt,0,0);
		if (elevating)
			translate(0,upOrDown * speed * dt,0);
		if (flying)
			translate(0,0,frontOrBack * speed * dt);
	}
}


NSMatrix4Df NSCamera::getRotationTransform()
{
	NSMatrix4Df rotateTransform;
	rotateTransform.setData(uvnMatrix.getRow(UVector), uvnMatrix.getRow(VVector), uvnMatrix.getRow(NVector));
	return rotateTransform;
}